package com.thot.timyag.core;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;

import com.thot.timyag.common.CommonUtil;
import com.thot.timyag.common.FileUtil;
import com.thot.timyag.config.properties.ThotMyBatisProperties;
import com.thot.timyag.config.properties.ThotProperties;
import com.thot.timyag.core.pathwatch.PathWatchHub;
import com.thot.timyag.core.pathwatch.PathWatcher;
import com.thot.timyag.exceptions.UpdateDependenceException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NestedIOException;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import sun.net.www.protocol.jar.Handler;

/**
 * @author yangjf
 * @time 2019-04-11 11:39
 * 管理类加载器
 */
@Component
@Slf4j
public class HotDeployContext implements Observer {

    private static String STR_JAR = "jar";

    private ThotProperties properties;

    private ThotMyBatisProperties myBatisProperties;

    private MapperHotDeployPlugin mapperHotDeployPlugin;

    @Getter
    private URLClassLoader classLoader;

    @Getter
    public String libPath;

    @Getter
    private List<String> registeredBeans = new ArrayList<>();

    @Getter
    private Set<ApiInfo> registerdMappings = new HashSet<>();

    @Autowired
    public HotDeployContext(PathWatchHub hub, ThotProperties properties, ThotMyBatisProperties myBatisProperties,
                            ApplicationContextProvider provider) {
        this.properties = properties;
        this.myBatisProperties = myBatisProperties;
        this.libPath = CommonUtil.getDefaultPath(this.properties.getLibPath());
        if (properties.isEnableAutoloading()) {
            hub.registerWatch(this.libPath, this);
        }
        init();
    }

    public void init() {
        if (null == classLoader) {
            this.classLoader = newClassLoader(libPath);
        }
        if (myBatisProperties.isEnable()) {
            reloadMapper();
        }
        List<String> beans = ApplicationContextProvider.scanBean(this.classLoader, properties.getScanPackage());
        this.registeredBeans = beans;
        System.out.println("loaded beans : " + this.registeredBeans);
        registerMappings();
    }

    public void reloadMapper() {
        if (null == mapperHotDeployPlugin) {
            SqlSessionFactory sessionFactory = ApplicationContextProvider.getBean(SqlSessionFactory.class);
            mapperHotDeployPlugin = new MapperHotDeployPlugin(sessionFactory.getConfiguration(), myBatisProperties, classLoader);
        } else {
            mapperHotDeployPlugin.setClassLoader(classLoader);
        }
        try {
            mapperHotDeployPlugin.reloadXml();
            mapperHotDeployPlugin.reloadMapperBean();
        } catch (NestedIOException e) {
            e.printStackTrace();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void uninstallJarDependence(String fileName) {
        this.cleanContext();
        FileUtil.deleteFile(fileName, this.libPath);
        if (!properties.isEnableAutoloading()) {
            //手动加载
            reloadClassLoaderAndClass(this.libPath);
        }
    }

    public File updateJarDependence(MultipartFile multipartFile) {
        File file;
        try {
            file = updateJarDependence(multipartFile.getBytes(), multipartFile.getOriginalFilename());
        } catch (IOException e) {
            log.error("update dependence error : " + e.getMessage());
            throw new UpdateDependenceException(e.getMessage());
        }
        if (!properties.isEnableAutoloading()) {
            //手动加载
            reloadClassLoaderAndClass(this.libPath);
        }
        return file;
    }

    public File updateJarDependence(byte[] bytes, String originalFilename) {
        try {
            File file = FileUtil.saveFile(bytes, originalFilename, libPath);
            return file;
        } catch (IOException e) {
            log.error("update dependence error : " + e.getMessage());
            throw new UpdateDependenceException(e.getMessage());
        }
    }

    public List<String> listDependence() {
        List<String> result = new ArrayList<>();
        List<File> files = pathToJarFile(libPath);
        if (!CollectionUtils.isEmpty(files)) {
            for (File file : files) {
                result.add(file.getName());
            }
        }
        return result;
    }

    public static List<File> pathToJarFile(String path) {
        File[] files = new File(path).listFiles();
        List<File> result = new ArrayList();
        for (File file : files) {
            if (!file.isDirectory() && StringUtils.getFilenameExtension(file.getName()).equals(STR_JAR)) {
                result.add(file);
            }
        }
        return result;
    }

    public static URL[] fileToUrl(List<File> files) {
        if (CollectionUtils.isEmpty(files)) {
            return new URL[0];
        }
        URL[] urls = new URL[files.size()];
        for (int i = 0; i < files.size(); i++) {
            try {
                urls[i] = new URL(files.get(i).toURI().toURL(), "jar:" + files.get(i).toURI().toString() + "!/", new Handler());
            } catch (MalformedURLException e) {
                log.error(e.getMessage());
            }
        }
        return urls;
    }

    public URLClassLoader newClassLoader(String path) {
        List<File> files = pathToJarFile(path);
        URL[] urls = fileToUrl(files);
        return new URLClassLoader(urls, HotDeployContext.class.getClassLoader());
    }

    public <T> T getBean(Class<T> clazz) {
        return ApplicationContextProvider.getBean(clazz);
    }

    public void removeBeans() {
        if (!CollectionUtils.isEmpty(registeredBeans)) {
            for (String name : registeredBeans) {
                //移除 handlerMapping
                ApplicationContextProvider.removeHandlerMappings(name);
                ApplicationContextProvider.removeBean(name);
            }
            registeredBeans.clear();
        }
    }

    public void cleanClassLoader() {
        if (null != classLoader) {
            try {
                classLoader.close();
                classLoader = null;
            } catch (IOException e) {
                log.error(e.getMessage());
            }
        }
    }

    public void cleanContext() {
        cleanClassLoader();
        removeBeans();
        if (null != mapperHotDeployPlugin) {
            mapperHotDeployPlugin.removeMapperBeans();
        }
    }

    public void reloadClassLoaderAndClass(String path) {
        System.out.println("reload");
        cleanContext();
        init();
    }

    private void registerMappings() {
        registerdMappings.clear();
        for (String beanName : registeredBeans) {
            registerdMappings.addAll(ApplicationContextProvider.initHandlerMethods(beanName));
        }
    }

    @Override
    public void update(Observable o, Object arg) {
        reloadClassLoaderAndClass(((PathWatcher) o).getWatchPath());
    }
}
